iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
自我挑戰組

嘗試30天學「不」會Rust系列 第 17

[Rust] 生命週期(Lifetime)

  • 分享至 

  • xImage
  •  

環境

OS: Windows 10
Editor: Visual Studio Code
Rust version: 1.63.0

借用檢查器(borrow checker)

首先先試試以下這段:

let r;

{
    let x = 5;
    r = &x;
}

println!("r: {}", r);
Compiling basic v0.1.0 (D:\projects\rust_learning\basic)
error[E0597]: `x` does not live long enough
   --> src\main.rs:103:13
    |
103 |         r = &x;
    |             ^^ borrowed value does not live long enough
104 |     }
    |     - `x` dropped here while still borrowed
105 |
106 |     println!("r: {}", r);
    |                       - borrow later used here

For more information about this error, try `rustc --explain E0597`.

r要借用x,但x在作用域之後就drop了,compiler直接標示 x活的不夠長,也就指向生命週期這個主題。

對於這件事,Rust會由它的借用檢查器(borrow checker)檢查這些借用是否還有效。

在函式中,最常發現我們的借用,在出了函式的作用域就消亡(drop)了。

fn longest(x: &str, y: &str) -> &str { // ERROR! 這裡會抱怨要回傳的借用離開作用域就消亡了
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// ...

fn main() {
    let x = longest("Monday", "Tuesday");
    println!("Result: {}", x);
}

執行以上程式,會給出這樣的錯誤:

Compiling basic v0.1.0 (D:\projects\rust_learning\basic)
error[E0106]: missing lifetime specifier
 --> src\main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.

然後compiler會建議在函式名稱、參數型別前面、回傳值前面,加上'a的符號,標示他們生命週期時間,這個是下面要介紹的生命週期詮釋語法

生命週期詮釋語法 (lifetime annotation)

生命週期詮釋語法會是以'為開頭,然後後面加上一個自己命名的變數名稱加到每個引用上,變數名稱大多會是'a'如同上面compiler錯誤中給的建議訊息,但其實想取什麼都可以,只要明確標示這些引用都是同個關係就好:

// 這樣也是可以的...
fn longest<'alpha>(x: &'alpha str, y: &'alpha str) -> &'alpha str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

使用上面修改過的範例,是可以正常執行的,我們成功修改了上面提到的錯誤,照著上面的論述,在每個引用加上生命週期的變數,我們讓函式中的引用都標示與函式有著一樣的時間,但這個是什麼呢?在函式名稱後面像泛型的語法:

fn longest<'alpha> // <-- what is this mean ?

這句表示是宣告生命週期的變數稱為'alpha'的意思,在longest這個函式裡面

注意! 生命週期詮釋語法只是明確地告訴compiler借用之間生命週期的關係,並不是改變借用的生命週期(存活時間)。

結構中的生命週期詮釋

知道了生命週期的詮釋,我們也可以在結構裡面使用引用型別的成員變數,標示這些引用成員是跟著Character消亡的:

struct Character<'a> {
    first_name: &'a str,
    last_name: &'a str,
}

fn main() {
    let character_name = String::from("Charlie Brown");
    
    let character = Character {
        first_name: &character_name[0..7],
        last_name: &character_name[7..13],
    };

    println!(
        "First name: {}, last name: {}",
        character.first_name, character.last_name
    );
}

生命週期省略

前幾天有一篇在嘗試理解Rust中String跟string literal的差別,最下面有一個範例:

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes(); // The type is &[u8]

    // 利用迭代器把序列的index跟element抓出來
    for (index, &element) in bytes.iter().enumerate() {
        if element == b' ' {
            return &s[0..index];
        }
    }

    &s[..]
}

在理解生命週期詮釋之後,就會覺得這個語法有些奇怪,回傳的是引用,那compiler為什麼沒有報錯呢,會是參數&String的問題嗎?改成&str看看:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes(); // The type is &[u8]

    // 利用迭代器把序列的index跟element抓出來
    for (index, &element) in bytes.iter().enumerate() {
        if element == b' ' {
            return &s[0..index];
        }
    }

    &s[..]
}

compiler是給過的。Why?在某些情況下可以對生命週期的詮釋進行省略。

以下則是關於Rust可以對生命週期詮釋進行省略的規則:

  1. 每個函式參數是引用的話,都會有自己一個生命週期變數,也就是說每個函式定義的時候都是長這樣fn bar<'a, 'b>(x: &'a i32, y: &'b i32),但Rust都讓我們把它省略了,所以最後才會是可以直接這樣用fn bar(x: &i32, y: &i32)
  2. 如果該函式只有一個參數,那著個參數的生命週期變數會加到所有輸出上,這個就是上面的範例會過的原因,我們只有一個參數,所以自動把輸出&str都被賦值(assign)生命週期變數。
  3. 如果是在實作方法,也就是參數會是有&self或是&mut self的情況,所有輸出都會有生命週期變數。

靜態引用(static reference)

如同其他語言中有static這個修飾詞,宣告為static的變數會在程式執行期間都存活著,Rust也一樣,:

static x: i32 = 100;

然後也可以把static用在生命週期的詮釋上:

let test: &'static str = "Hello";

因為標記為static,這樣做會是可以的:

let x: &str;
{
    let test: &'static str = "Hello";
    x = test;
}

println!("{}", x);

Reference


上一篇
[Rust] 特徵 (Trait)
下一篇
[Rust] Function programming in Rust - 閉包
系列文
嘗試30天學「不」會Rust18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言